using System;

namespace PropertyExpression.Common
{
    public interface IChoice<out T1, out T2>
    {
        T Match<T>(Func<T1, T> choice1, Func<T2, T> choice2);
        bool IsChoice1 { get; }
        bool IsChoice2 { get; }
        T1 Value1 { get; }
        T2 Value2 { get; }
    }

    public static class Choice
    {
        public static IChoice<T1, T2> New<T1, T2>(T1 choice1, Type<T2> choice2)
        {
            return new Choice1<T1, T2>(choice1);
        }

        public static IChoice<T1, T2> New<T1, T2>(Type<T1> choice1, T2 choice2)
        {
            return new Choice2<T1, T2>(choice2);
        }

        public static void Match<T1, T2>(this IChoice<T1, T2> it, Action<T1> choice1, Action<T2> choice2)
        {
            if (it.IsChoice1) choice1(it.Value1);
            else choice2(it.Value2);
        }

        private class Choice1<T1, T2> : IChoice<T1, T2>
        {
            private readonly T1 value;

            public Choice1(T1 value)
            {
                this.value = value;
            }

            public T Match<T>(Func<T1, T> choice1, Func<T2, T> choice2)
            {
                return choice1(value);
            }

            public bool IsChoice1
            {
                get { return true; }
            }

            public bool IsChoice2
            {
                get { return false; }
            }

            public T1 Value1
            {
                get { return value; }
            }

            public T2 Value2
            {
                get { throw new InvalidOperationException(); }
            }
        }

        private class Choice2<T1, T2> : IChoice<T1, T2>
        {
            private readonly T2 value;

            public Choice2(T2 value)
            {
                this.value = value;
            }

            public T Match<T>(Func<T1, T> choice1, Func<T2, T> choice2)
            {
                return choice2(value);
            }

            public bool IsChoice1
            {
                get { return false; }
            }

            public bool IsChoice2
            {
                get { return true; }
            }

            public T1 Value1
            {
                get { throw new InvalidOperationException(); }
            }

            public T2 Value2
            {
                get { return value; }
            }
        }
    }
}